package com.gitee.dolt.security.token;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.gitee.dolt.cache.repository.CacheOps;
import com.gitee.dolt.core.context.ContextConstants;
import com.gitee.dolt.core.exception.BizException;
import com.gitee.dolt.core.exception.code.ExceptionCode;
import com.gitee.dolt.utils.DateUtils;
import com.gitee.dolt.security.model.AuthInfo;
import com.gitee.dolt.security.model.SysToken;
import com.gitee.dolt.security.model.SysUser;
import com.gitee.dolt.security.properties.SecurityProperties;
import io.jsonwebtoken.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

/**
 * @author LYH
 */

@RequiredArgsConstructor
@Slf4j
public class TokenProvider {

    private final SecurityProperties securityProperties;
    private final CacheOps cacheOps;
    private final List<TokenClaimsIntervention> tokenClaimsInterventions;
    protected static final String DIVIDER = ";";

    /**
     * 生成token
     *
     * @param sysUser 用户信息
     * @return
     */
    public AuthInfo createAuthInfo(SysUser sysUser) {
        return createAuthInfo(sysUser, null);
    }

    /**
     * 生成token
     *
     * @param sysUser 用户信息
     * @param expire  有效时间（秒）
     * @return
     */
    public AuthInfo createAuthInfo(SysUser sysUser, Long expire) {
        SecurityProperties.JwtProperties jwt = securityProperties.getJwt();
        if (expire == null || expire == 0) {
            expire = securityProperties.getJwt().getExpire();
        }
        Date now = new Date();
        // 到期时间
        Date exp;
        if (expire == null || expire == 0) {
            // 第二天三点
            exp = DateUtil.endOfDay(now).offset(DateField.HOUR, 3);
            expire = DateUtil.between(now, exp, DateUnit.SECOND);
        } else {
            exp = DateUtil.offsetSecond(now, Math.toIntExact(expire));
        }

        JwtBuilder jwtBuilder = Jwts.builder();


        jwtBuilder.setHeaderParam("typ", "JsonWebToken")
                .signWith(SignatureAlgorithm.HS256, jwt.getSecret())
                // 发布时间
                .setIssuedAt(now)
                // token从时间什么开始生效
                .setNotBefore(now)
                // token从什么时间截止生效
                .setExpiration(exp)
                // 参数
                .claim(ContextConstants.JWT_KEY_TOKEN_EXPIRE, expire)
                .claim(ContextConstants.JWT_KEY_TOKEN_TYPE, ContextConstants.BEARER_HEADER_KEY)
                .claim(ContextConstants.JWT_KEY_USER_ID, Convert.toStr(sysUser.getId(), "0"))
                .claim(ContextConstants.JWT_KEY_ACCOUNT, sysUser.getAccount())
                .claim(ContextConstants.JWT_KEY_NAME, sysUser.getName())
                .claim(ContextConstants.JWT_KEY_SCOPE, sysUser.getScope())
                .claim(ContextConstants.JWT_KEY_ROLE, CollUtil.join(sysUser.getRoles(), DIVIDER))
                .claim(ContextConstants.JWT_KEY_RESOURCE, CollUtil.join(sysUser.getResources(), DIVIDER));

        if (CollUtil.isNotEmpty(tokenClaimsInterventions)) {
            tokenClaimsInterventions.forEach(tokenClaimsIntervention -> jwtBuilder.addClaims(tokenClaimsIntervention.claims(sysUser)));
        }

        // 生成token
        String token = jwtBuilder.compact();

        // 组装Token信息
        SysToken sysToken = new SysToken()
                .setToken(token)
                .setExpire(expire)
                .setTokenType(ContextConstants.BEARER_HEADER_KEY)
                .setExpiration(DateUtils.date2LocalDateTime(exp));
        return new AuthInfo().setUser(sysUser).setToken(sysToken);
    }

    /**
     * 刷新token
     *
     * @param token
     * @return
     */
    public AuthInfo refreshAuthInfo(String token) {
        Claims claims = parseJwt(token);
        SysUser sysUser = getUserFromClaims(claims);
        Long expire = Convert.toLong(claims.get(ContextConstants.JWT_KEY_TOKEN_EXPIRE));
        return createAuthInfo(sysUser, expire);
    }

    /**
     * 获取token信息
     *
     * @param token
     * @return
     */
    public AuthInfo getAuthInfo(String token) {
        // 是否启用的短token
        if (securityProperties.getJwt().getShortToken()) {
            token = cacheOps.get(ContextConstants.SHORT_TOKEN_PREFIX + ":" + token,
                    false);
            if (StrUtil.isEmpty(token)) {
                throw new BizException(ExceptionCode.JWT_SIGNATURE);
            }
        }

        Claims claims = parseJwt(token);
        SysUser sysUser = getUserFromClaims(claims);
        String tokenType = Convert.toStr(claims.get(ContextConstants.JWT_KEY_TOKEN_TYPE));
        AuthInfo authInfo = new AuthInfo()
                .setUser(sysUser)
                .setToken(new SysToken().setToken(token).setTokenType(tokenType));
        return authInfo;
    }

    private SysUser getUserFromClaims(Claims claims) {
        String userId = Convert.toStr(claims.get(ContextConstants.JWT_KEY_USER_ID));
        String account = Convert.toStr(claims.get(ContextConstants.JWT_KEY_ACCOUNT));
        String name = Convert.toStr(claims.get(ContextConstants.JWT_KEY_NAME));
        String scope = Convert.toStr(claims.get(ContextConstants.JWT_KEY_SCOPE));
        List<String> roles = StrUtil.split(Convert.toStr(claims.get(ContextConstants.JWT_KEY_ROLE)), Convert.toChar(DIVIDER));
        List<String> resources = StrUtil.split(Convert.toStr(claims.get(ContextConstants.JWT_KEY_RESOURCE)), Convert.toChar(DIVIDER));
        SysUser sysUser = new SysUser().setId(userId).setAccount(account).setName(name).setScope(scope).setRoles(roles).setResources(resources);
        if (CollUtil.isNotEmpty(tokenClaimsInterventions)) {
            tokenClaimsInterventions.forEach(tokenClaimsIntervention -> tokenClaimsIntervention.injectFields().forEach(field -> sysUser.set(field, claims.get(field))));
        }
        return sysUser;
    }

    private Claims parseJwt(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(securityProperties.getJwt().getSecret())
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException ex) {
            log.warn("token 过期 。 ", ex);
            //过期
            throw new BizException(ExceptionCode.JWT_TOKEN_EXPIRED);
        } catch (SignatureException ex) {
            log.warn("token 签名错误", ex);
            //签名错误
            throw new BizException(ExceptionCode.JWT_SIGNATURE);
        } catch (IllegalArgumentException ex) {
            log.warn("token 为空", ex);
            //token 为空
            throw new BizException(ExceptionCode.JWT_ILLEGAL_ARGUMENT);
        } catch (Exception e) {
            log.error("errCode:{}, message:{}, token:{}", ExceptionCode.JWT_PARSER_TOKEN_FAIL.getCode(), e.getMessage(), token, e);
            throw new BizException(ExceptionCode.JWT_PARSER_TOKEN_FAIL);
        }
    }

}
